﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using System.IO;
using Microsoft.Xna.Framework.Input;
using Platformer1.GameScreens;

namespace Platformer1
{
    /// <summary>
    /// A uniform grid of tiles with collections of gems and enemies.
    /// The level owns the player and controls the game's win and lose
    /// conditions as well as scoring.
    /// </summary>
    class Level : IDisposable
    {
        // Physical structure of the level.
        private Tile[,] tiles;
        private Layer[] layers;
        public List<Fireball> fireballs = new List<Fireball>();
        public float cameraPosition;
        public float cameraHeight;
        // The layer which entities are drawn on top of.
        private const int EntityLayer = 2;
        public Unicorn unicorn;
        // Entities in the level.
        public int view_width = 0;
        public float lastSpawn = 5;
        public double deathTime = 0;
        public double herbertTime = 0;

        protected SoundEffectInstance soundByte;
        protected SoundState getSoundByte { get { return soundByte.State; } }
        protected SoundEffectInstance track0, track1, track2, track3, track4, track5, track6, track7;
        public static int tracknumber = 0;

        public Player[] Players
        {
            get { return players; }
        }
        public Player getClosestPlayer(Vector2 position)
        {
            float distMin = float.MaxValue;
            int minRef = 0;
            float[] distances = new float[players.Length];
            for(int i=0; i<players.Length; i++)
            {
                Player p = players[i];
                distances[i] = (position - p.Position).LengthSquared();

                if (distances[i] < distMin)
                {
                    distMin = distances[i];
                    minRef = i;
                }
            }

            return players[minRef];
        }
        Player[] players;

        private List<Gem> gems = new List<Gem>();
        private List<Enemy> enemies = new List<Enemy>();
        private List<CustomEnemy> myEnemies = new List<CustomEnemy>();
        private List<Powerup> powerups = new List<Powerup>();
        private Spark sp;

        // Key locations in the level.        
        private Vector2 start;
        private Point exit = InvalidPosition;
        private static readonly Point InvalidPosition = new Point(-1, -1);

        // Level game state.
        private Random random = new Random(354668); // Arbitrary, but constant seed

        public int Score
        {
            get { return score; }
        }
        int score;

        public bool ReachedExit
        {
            get { return reachedExit; }
        }
        bool reachedExit;

        public TimeSpan TimeRemaining
        {
            get { return timeRemaining; }
        }
        TimeSpan timeRemaining;

        private const int PointsPerSecond = 5;

        // Level content.        
        public ContentManager Content
        {
            get { return content; }
        }
        ContentManager content;

        private SoundEffect exitReachedSound;

        #region Loading

        /// <summary>
        /// Constructs a new level.
        /// </summary>
        /// <param name="serviceProvider">
        /// The service provider that will be used to construct a ContentManager.
        /// </param>
        /// <param name="path">
        /// The absolute path to the level file to be loaded.
        /// </param>
        public Level(IServiceProvider serviceProvider, string path)
        {
            // Create a new content manager to load content used just by this level.
            content = new ContentManager(serviceProvider, "Content");

            timeRemaining = TimeSpan.FromMinutes(2.0);

            LoadTiles(path);

            // Load background layer textures. For now, all levels must
            // use the same backgrounds and only use the left-most part of them.
            /*layers = new Texture2D[3];
            for (int i = 0; i < layers.Length; ++i)
            {
                // Choose a random segment if each background layer for level variety.
                int segmentIndex = random.Next(3);
                layers[i] = Content.Load<Texture2D>("Backgrounds/Layer" + i + "_" + segmentIndex);
            }
            */
            layers = new Layer[3];
            layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
            layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
            layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);

            // Load sounds.
            exitReachedSound = Content.Load<SoundEffect>("Sounds/ExitReached");

            //loading sounds
            track0 = Content.Load<SoundEffect>("Sounds/Herbert/track0").CreateInstance();
            track1 = Content.Load<SoundEffect>("Sounds/Herbert/track1").CreateInstance();
            track2 = Content.Load<SoundEffect>("Sounds/Herbert/track2").CreateInstance();
            track3 = Content.Load<SoundEffect>("Sounds/Herbert/track3").CreateInstance();
            track4 = Content.Load<SoundEffect>("Sounds/Herbert/track4").CreateInstance();
            track5 = Content.Load<SoundEffect>("Sounds/Herbert/track5").CreateInstance();
            track6 = Content.Load<SoundEffect>("Sounds/Herbert/track6").CreateInstance();

            soundByte = track0;
        }

        /// <summary>
        /// Iterates over every tile in the structure file and loads its
        /// appearance and behavior. This method also validates that the
        /// file is well-formed with a player start point, exit, etc.
        /// </summary>
        /// <param name="path">
        /// The absolute path to the level file to be loaded.
        /// </param>
        private void LoadTiles(string path)
        {
            // Load the level and ensure all of the lines are the same length.
            int width;
            List<string> lines = new List<string>();
            using (StreamReader reader = new StreamReader(TitleContainer.OpenStream(path)))
            {
                string line = reader.ReadLine();
                width = line.Length;
                while (line != null)
                {
                    lines.Add(line);
                    if (line.Length != width)
                        throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
                    line = reader.ReadLine();
                }
            }

            // Allocate the tile grid.
            tiles = new Tile[width, lines.Count];

            // Loop over every tile position,
            for (int y = 0; y < Height; ++y)
            {
                for (int x = 0; x < Width; ++x)
                {
                    // to load each tile.
                    char tileType = lines[y][x];
                    tiles[x, y] = LoadTile(tileType, x, y);
                }
            }

            // Verify that the level has a beginning and an end.
            if (Players == null)
                throw new NotSupportedException("A level must have a starting point.");
            if (exit == InvalidPosition)
                throw new NotSupportedException("A level must have an exit.");

        }

        /// <summary>
        /// Loads an individual tile's appearance and behavior.
        /// </summary>
        /// <param name="tileType">
        /// The character loaded from the structure file which
        /// indicates what should be loaded.
        /// </param>
        /// <param name="x">
        /// The X location of this tile in tile space.
        /// </param>
        /// <param name="y">
        /// The Y location of this tile in tile space.
        /// </param>
        /// <returns>The loaded tile.</returns>
        private Tile LoadTile(char tileType, int x, int y)
        {
            switch (tileType)
            {
                // Blank space
                case '.':
                    return new Tile(null, TileCollision.Passable);

                // Exit
                case 'X':
                    return LoadExitTile(x, y);

                // Gem
                case 'G':
                    return LoadGemTile(x, y);

                // Floating platform
                case '-':
                    return LoadTile("Platform", TileCollision.Platform);

                // Various enemies
                case 'A':
                    return LoadCustomEnemyTile(x, y, "MonsterA");
                case 'B':
                    return LoadCustomEnemyTile(x, y, "MonsterA");
                case 'C':
                    return LoadCustomEnemyTile(x, y, "MonsterC");
                case 'D':
                    return LoadCustomEnemyTile(x, y, "MonsterA");

                // Powerups 
                case 'P':
                    return LoadPowerUpTile(x, y, "Shroom");
                case 'O':
                    return LoadPowerUpTile(x, y, "Bottle");

                // Platform block
                case '~':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Platform);

                // Passable block
                case ':':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Passable);

                // Player 1 start point
                case '1':
                    return LoadStartTile(x, y);

                // Impassable block
                case '#':
                    return LoadVarietyTile("BlockA", 7, TileCollision.Impassable);

              case 'V':
                    return LoadVarietyTile("SpikeB", 3, TileCollision.Spikes);
              case '^':
                    return LoadVarietyTile("SpikeA", 3, TileCollision.Spikes);
              case '<':
                    return LoadVarietyTile("SpikeD", 3, TileCollision.Spikes);
              case '>':
                    return LoadVarietyTile("SpikeC", 3, TileCollision.Spikes);
              case 'F':
                    return LoadVarietyTile("BlockF", 3, TileCollision.Flames);
              case 'Z':
                    return LoadVarietyTile("BlockF", 3, TileCollision.Flames);
              // Unknown tile type character
                default:
                    throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, x, y));
            }
        }

        /// <summary>
        /// Creates a new tile. The other tile loading methods typically chain to this
        /// method after performing their special logic.
        /// </summary>
        /// <param name="name">
        /// Path to a tile texture relative to the Content/Tiles directory.
        /// </param>
        /// <param name="collision">
        /// The tile collision type for the new tile.
        /// </param>
        /// <returns>The new tile.</returns>
        private Tile LoadTile(string name, TileCollision collision)
        {
            return new Tile(Content.Load<Texture2D>("Tiles/" + name), collision);
        }


        /// <summary>
        /// Loads a tile with a random appearance.
        /// </summary>
        /// <param name="baseName">
        /// The content name prefix for this group of tile variations. Tile groups are
        /// name LikeThis0.png and LikeThis1.png and LikeThis2.png.
        /// </param>
        /// <param name="variationCount">
        /// The number of variations in this group.
        /// </param>
        private Tile LoadVarietyTile(string baseName, int variationCount, TileCollision collision)
        {
            int index = random.Next(variationCount);
            return LoadTile(baseName + index, collision);
        }

        private Tile LoadCustomEnemyTile(int x, int y, string spriteSet)
        {
            if (spriteSet.Equals("MonsterC"))
            {
                Vector2 position = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
                myEnemies.Add(new OliveEnemy(this, position));
            }
            else if (spriteSet.Equals("MonsterA"))
            {
                Vector2 position = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
                myEnemies.Add(new NinjaEnemy(this, position));
            }
            return new Tile(null, TileCollision.Passable);
        }   
 
        /// <summary>
        /// Instantiates a player, puts him in the level, and remembers where to put him when he is resurrected.
        /// </summary>
        private Tile LoadStartTile(int x, int y)
        {
            start = RectangleExtensions.GetBottomCenter(GetBounds(x, y));

            Player p1, p2;
            /*
            p1 = new Ryu(this, start, PlayerIndex.One);
            if (GamePad.GetState(PlayerIndex.Two).IsConnected)
            {
                p2 = new Ryu(this, start, PlayerIndex.Two);
                players = new Player[2];
                players[0] = p1;
                players[1] = p2;
            }
            else
            {
                players = new Player[1];
                players[0] = p1;
            }
             * */
            p1 = new OliveMan(this, start, PlayerIndex.One);
            players = new Player[1];
            players[0] = p1;
            
            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Remembers the location of the level's exit.
        /// </summary>
        private Tile LoadExitTile(int x, int y)
        {
            if (exit != InvalidPosition)
                throw new NotSupportedException("A level may only have one exit.");

            exit = GetBounds(x, y).Center;

            return LoadTile("Exit", TileCollision.Passable);
        }

        /// <summary>
        /// Instantiates an enemy and puts him in the level.
        /// </summary>
        private Tile LoadEnemyTile(int x, int y, string spriteSet)
        {
            Vector2 position = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
            enemies.Add(new Enemy(this, position, spriteSet));

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Instantiates a powerup and puts it in the level.
        /// </summary>
        private Tile LoadPowerUpTile(int x, int y, string spriteset)
        {
            Point position = GetBounds(x, y).Center;
            powerups.Add(new Powerup(this, new Vector2(position.X, position.Y)));

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Instantiates a gem and puts it in the level.
        /// </summary>
        private Tile LoadGemTile(int x, int y)
        {
            Point position = GetBounds(x, y).Center;
            gems.Add(new Gem(this, new Vector2(position.X, position.Y)));

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Unloads the level content.
        /// </summary>
        public void Dispose()
        {
            Content.Unload();
        }

        #endregion

        #region Bounds and collision

        /// <summary>
        /// Gets the collision mode of the tile at a particular location.
        /// This method handles tiles outside of the levels boundries by making it
        /// impossible to escape past the left or right edges, but allowing things
        /// to jump beyond the top of the level and fall off the bottom.
        /// </summary>
        public TileCollision GetCollision(int x, int y)
        {
            // Prevent escaping past the level ends.
            if (x < 0 || x >= Width)
                return TileCollision.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return TileCollision.Passable;

            return tiles[x, y].Collision;
        }

        /// <summary>
        /// Gets the bounding rectangle of a tile in world space.
        /// </summary>        
        public Rectangle GetBounds(int x, int y)
        {
            return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
        }

        /// <summary>
        /// Width of level measured in tiles.
        /// </summary>
        public int Width
        {
            get { return tiles.GetLength(0); }
        }

        /// <summary>
        /// Height of the level measured in tiles.
        /// </summary>
        public int Height
        {
            get { return tiles.GetLength(1); }
        }

        #endregion

        #region Update

        /// <summary>
        /// Updates all objects in the world, performs collision between them,
        /// and handles the time limit with scoring.
        /// </summary>
        public void Update(GameTime gameTime)
        {
          if(deathTime > 0)
            deathTime -= gameTime.ElapsedGameTime.TotalSeconds;

          if (herbertTime > 0)
            herbertTime -= gameTime.ElapsedGameTime.TotalSeconds;

          if (soundByte == null)
          {
            soundByte = track0;
            tracknumber = 0;
          }

          if (getSoundByte == SoundState.Stopped && herbertTime <= 0)
          {
            herbertTime = 30;
            switch (tracknumber)
            {
              case 0:
                soundByte = track0;
                tracknumber = 1;
                break;
              case 1:
                soundByte = track1;
                tracknumber = 2;
                break;
              case 2:
                soundByte = track2;
                tracknumber = 3;
                break;
              case 3:
                soundByte = track3;
                tracknumber = 4;
                break;
              case 4:
                soundByte = track4;
                tracknumber = 5;
                break;
              case 5:
                soundByte = track5;
                tracknumber = 6;
                break;
              case 6:
                soundByte = track6;
                tracknumber = 0;
                break;
            }
            //soundByte.Play();
          }
          
                // Pause while the player is dead or time is expired.
                if (!players[0].IsAlive || TimeRemaining == TimeSpan.Zero)
                {
                    // Still want to perform physics on the player.
                    foreach (Player player in players)
                    {
                        player.ApplyPhysics(gameTime);
                    }
                    foreach (CustomEnemy e in myEnemies)
                    {
                        e.ApplyPhysics(gameTime);
                    }
                }
                else if (ReachedExit)
                {
                    // Animate the time being converted into points.
                    int seconds = (int)Math.Round(gameTime.ElapsedGameTime.TotalSeconds * 100.0f);
                    seconds = Math.Min(seconds, (int)Math.Ceiling(TimeRemaining.TotalSeconds));
                    timeRemaining -= TimeSpan.FromSeconds(seconds);
                    score += seconds * PointsPerSecond;
                    
                }
                else
                {
                    timeRemaining -= gameTime.ElapsedGameTime;
                    /*------------------*/
                    
                    // remove obama bomb
                    /*
                    lastSpawn = Math.Max(0.0f, lastSpawn - (float)gameTime.ElapsedGameTime.TotalSeconds);
                    if (lastSpawn <= 0)
                    {

                        int left = (int)Math.Floor(cameraPosition / Tile.Width);
                        int right = left + view_width / Tile.Width;
                        right = Math.Min(right, Width - 1);

                        Random r = new Random();
                        int amount_of_enemies = r.Next(0,1);
                        for (int i = 0; i <= amount_of_enemies; i++)
                        {
                            float enemy_x_Position = r.Next(left, right);
                            enemy_x_Position *= Tile.Width;

                            Obama oe = new Obama(this, new Vector2(enemy_x_Position, 0f));
                            myEnemies.Add(oe);

                        }
                        lastSpawn = 5;
                    }
                    /*------------------*/
                    if (unicorn != null)
                    {
                        unicorn.Update(gameTime);
                    }

                    foreach (Player player in players)
                    {
                        player.Update(gameTime);
                    }

                    foreach (CustomEnemy e in myEnemies)
                    {
                        e.Update(gameTime);
                    }

                    UpdateGems(gameTime);

                    // Falling off the bottom of the level kills the player.
                    foreach (Player player in players)
                    {
                        if (player.BoundingRectangle.Top >= Height * Tile.Height)
                        {
                            OnPlayerKilled(player, null);

                        }
                    }

                    foreach (Fireball fire in fireballs)
                    {
                        fire.Update(gameTime);
                        foreach (CustomEnemy cunt in myEnemies)
                        {
                            if (fire.type.Equals("HADUKEN"))
                            {
                                if (cunt.IsAlive && fire.bActive)
                                {
                                    if (fire.BoundingCircle.Intersects(cunt.BoundingRectangle))
                                    {
                                        cunt.OnKilled(null);
                                        fire.bActive = false;

                                    }
                                }
                            }
                        }

                    }

                    UpdateEnemies(gameTime);

                    // The player has reached the exit if they are standing on the ground and
                    // his bounding rectangle contains the center of the exit tile. They can only
                    // exit when they have collected all of the gems.
                    foreach (Player player in players)
                    {
                        if (player.IsAlive &&
                            player.IsOnGround &&
                            player.BoundingRectangle.Contains(exit))
                        {
                            OnExitReached(player);
                        }
                    }
                }

                foreach (Player player in players)
                {
                    Rectangle hitBox = player.getCurrentHitBox();
                    if (hitBox != Rectangle.Empty)
                    {
                        foreach (CustomEnemy e in myEnemies)
                        {
                            if (hitBox.Intersects(e.BoundingRectangle))
                            {
                                //kill the bitch
                                e.OnKilled(null);
                            }
                        }
                    }
                }

                if (unicorn != null)
                {
                    foreach (CustomEnemy e in myEnemies)
                    {
                        Rectangle enemyPunch = e.getCurrentHitBox();
                        if (enemyPunch != Rectangle.Empty)
                        {
                            foreach (Player p in players)
                            {
                                if (enemyPunch.Intersects(p.BoundingRectangle))
                                {
                                    p.health -= 1;
                                    if (p.health <= 0)
                                    {

                                        OnPlayerKilled(p, null);
                                        p.health = 100;
                                    }
                                }
                            }
                        }
                    }
                }
                foreach (CustomEnemy e in myEnemies)
                {
                    Rectangle enemyPunch = e.getCurrentHitBox();
                    if (enemyPunch != Rectangle.Empty)
                    {
                        foreach (Player p in players)
                        {
                            if (enemyPunch.Intersects(p.BoundingRectangle))
                            {
                                if (e.eType.Equals("CORN"))
                                {
                                    p.health -= 50;

                                }
                                else
                                {
                                    p.health -= 1;
                                }

                                if (p.health <= 0)
                                {

                                    OnPlayerKilled(p, null);
                                    p.health = 100;
                                }
                            }
                        }
                    }
                }


            // Clamp the time remaining at zero.
            if (timeRemaining < TimeSpan.Zero)
                timeRemaining = TimeSpan.Zero;
        }

        /// <summary>
        /// Animates each gem and checks to allows the player to collect them.
        /// </summary>
        private void UpdateGems(GameTime gameTime)
        {
            foreach (Player player in players)
            {
                for (int i = 0; i < gems.Count; ++i)
                {
                    Gem gem = gems[i];

                    gem.Update(gameTime);

                    if (gem.BoundingCircle.Intersects(player.BoundingRectangle))
                    {
                        gems.RemoveAt(i--);
                        OnGemCollected(gem, player);
                    }
                }
                for (int i = 0; i < powerups.Count; ++i)
                {
                    Powerup powerup = powerups[i];

                    powerup.Update(gameTime);

                    if (powerup.BoundingCircle.Intersects(player.BoundingRectangle))
                    {
                        powerups.RemoveAt(i--);
                        OnGemCollected(powerup, player);
                    }
                }
            }
        }


        /// <summary>
        /// Animates each enemy and allow them to kill the player.
        /// </summary>
        private void UpdateEnemies(GameTime gameTime)
        {
            foreach (Enemy enemy in enemies)
            {
                enemy.Update(gameTime);

                // Touching an enemy instantly kills the player
                foreach (Player player in players)
                {
                    if (enemy.BoundingRectangle.Intersects(player.BoundingRectangle))
                    {
                        OnPlayerKilled(player, enemy);
                    }
                }
            }
        }

        /// <summary>
        /// Called when a gem is collected.
        /// </summary>
        /// <param name="gem">The gem that was collected.</param>
        /// <param name="collectedBy">The player who collected this gem.</param>
        private void OnGemCollected(Gem gem, Player collectedBy)
        {
            score += Gem.PointValue;
            collectedBy.bHODUKEN_READY = true;
            gem.OnCollected(collectedBy);
        }

        /// <summary>
        /// Called when the player is killed.
        /// </summary>
        /// <param name="killedBy">
        /// The enemy who killed the player. This is null if the player was not killed by an
        /// enemy, such as when a player falls into a hole.
        /// </param>
        private void OnPlayerKilled(Player player, Enemy killedBy)
        {
            player.OnKilled(killedBy);
        }

        /// <summary>
        /// Called when the player reaches the level's exit.
        /// </summary>
        private void OnExitReached(Player player)
        {
            player.OnReachedExit();
            exitReachedSound.Play();
            reachedExit = true;
        }

        /// <summary>
        /// Restores the player to the starting point to try the level again.
        /// </summary>
        public void StartNewLife(Player player)
        {
            player.Reset(start);
        }

        #endregion

        #region Draw

        /// <summary>
        /// Draw everything in the level from background to foreground.
        /// </summary>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            spriteBatch.Begin();

            for (int i = 0; i <= EntityLayer; ++i)
                //spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);
                layers[i].Draw(spriteBatch, cameraPosition);
            spriteBatch.End();

            ScrollCamera(spriteBatch.GraphicsDevice.Viewport);

            Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition, -cameraHeight, 0.0f);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cameraTransform);

            DrawTiles(spriteBatch);

            foreach (Gem gem in gems)
                gem.Draw(gameTime, spriteBatch);

            foreach (Player player in players)
            {
                player.Draw(gameTime, spriteBatch);
            }

            if (unicorn != null)
            {
                unicorn.Draw(gameTime, spriteBatch);
            }

            foreach (CustomEnemy e in myEnemies)
            {
                e.Draw(gameTime, spriteBatch);
            }

            foreach (Powerup powerup in powerups)
                powerup.Draw(gameTime, spriteBatch);

            foreach (Fireball fire in fireballs)
                fire.Draw(gameTime, spriteBatch);

            foreach (Enemy enemy in enemies)
                enemy.Draw(gameTime, spriteBatch);
            spriteBatch.End();

            spriteBatch.Begin();
            for (int i = EntityLayer + 1; i < layers.Length; ++i)
                layers[i].Draw(spriteBatch, cameraPosition);
            //spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);
            
            //for (int i = 0; i <= EntityLayer; ++i)
                //spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);
            spriteBatch.End();

            /*
            lineBatch.Begin(Camera.SimProjection, Camera.SimView);
            // draw ground
            for (int i = 0; i < _ground.FixtureList.Count; ++i)
            {
                lineBatch.DrawLineShape(_ground.FixtureList[i].Shape, Color.Black);
            }
            lineBatch.End();*/
        }

        /// <summary>
        /// Draws each tile in the level.
        /// </summary>
        private void DrawTiles(SpriteBatch spriteBatch)
        {
            // Calculate the visible range of tiles.
            int left = (int)Math.Floor(cameraPosition / Tile.Width);
            int right = left + spriteBatch.GraphicsDevice.Viewport.Width / Tile.Width;
            right = Math.Min(right, Width - 1);
            view_width = spriteBatch.GraphicsDevice.Viewport.Width;

            // For each tile position
            for (int y = 0; y < Height; ++y)
            {
                for (int x = left; x <= right; ++x)
                {
                    // If there is a visible tile in that position
                    Texture2D texture = tiles[x, y].Texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x, y) * Tile.Size;
                        spriteBatch.Draw(texture, position, Color.White);
                    }
                }
            }
        }


        private void ScrollCamera(Viewport viewport)
        {
            const float ViewMargin = 0.35f;
            // Calculate the edges of the screen.
            float marginWidth = viewport.Width * ViewMargin;
            float marginHeight = viewport.Height * ViewMargin;
            float marginLeft = cameraPosition + marginWidth;
            float marginRight = cameraPosition + viewport.Width - marginWidth;
            float marginBottom = cameraHeight + viewport.Height - marginHeight;
            float marginTop = cameraHeight + marginHeight;

            // Calculate how far to scroll when the player is near the edges of the screen.
            float cameraMovement = 0.0f;
            if (Players[0].Position.X < marginLeft)
                cameraMovement = Players[0].Position.X - marginLeft;
            else if (Players[0].Position.X > marginRight)
                cameraMovement = Players[0].Position.X - marginRight;

            // Update the camera position, but prevent scrolling off the ends of the level.
            float maxCameraPosition = Tile.Width * Width - viewport.Width;
            cameraPosition = MathHelper.Clamp(cameraPosition + cameraMovement, 0.0f, maxCameraPosition);

            cameraMovement = 0.0f;
            if (Players[0].Position.Y < marginTop)
                cameraMovement = Players[0].Position.Y - marginTop;
            else if (Players[0].Position.Y > marginBottom)
                cameraMovement = Players[0].Position.Y - marginBottom;

            float maxCameraHeight = Tile.Height * Height - viewport.Height;
            cameraHeight = MathHelper.Clamp(cameraHeight + cameraMovement, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height/-2, maxCameraHeight);
        }

        #endregion
    }
}
